namespace Game {
    using UnityEngine;
    using System.Collections.Generic;

    /// <summary>
    ///     Simple timer.
    /// </summary>
    public class Timer : MonoBehaviour {

        /// <summary>
        ///     Initailize global timer proxy.
        /// </summary>
        public static void Initialize() {
            if (ins != null) return;

            GameObject proxy = new GameObject("TimerProxy");
            ins = proxy.AddComponent<Timer>();
            DontDestroyOnLoad(proxy);
        }

        /// <summary>
        ///     Create a new timer.
        /// </summary>
        /// <param name="delay">Delay time for job</param>
        /// <param name="proc">Job</param>
        /// <param name="ignoreTimeScale">Using real time?</param>
        /// <returns>Id to control this timer.</returns>
        public static uint Add(float delay, System.Action proc, bool ignoreTimeScale = true) {
            if (ins == null) {
                Logger.Error("--> Timer.Add can NOT be called before Timer.Initialize");
                return 0;
            }

            Info info = new Info();
            info.loop = 0;
            info.ignoreTimeScale = ignoreTimeScale;
            info.proc = proc;
            info.next = delay;

            uint id = (++allocId);
            info.id = id;
            timers.Add(id, info);
            return id;
        }

        /// <summary>
        ///     Create a loop timer.
        /// </summary>
        /// <param name="delay">Delay time for first run</param>
        /// <param name="loop">Loop time for next run</param>
        /// <param name="proc">Job</param>
        /// <param name="ignoreTimeScale">Using real time?</param>
        /// <returns>Id to control this timer</returns>
        public static uint Add(float delay, float loop, System.Action proc, bool ignoreTimeScale = true) {
            if (ins == null) {
                Logger.Error("--> Timer.Add can NOT be called before Timer.Initialize");
                return 0;
            }

            Info info = new Info();
            info.loop = loop;
            info.ignoreTimeScale = ignoreTimeScale;
            info.proc = proc;
            info.next = delay;

            uint id = (++allocId);
            info.id = id;
            timers.Add(id, info);
            return id;
        }

        /// <summary>
        ///     Delete a timer.
        /// </summary>
        /// <param name="id">Unique id to find this timer.</param>
        /// <param name="finish">Force finish before delete?</param>
        public static void Del(uint id, bool finish = false) {
            Info info = null;
            if (timers.TryGetValue(id, out info)) {
                if (finish) info.proc.Invoke();
                timers.Remove(id);
            }
        }

        void FixedUpdate() {
            float unscaled = Time.unscaledDeltaTime;
            float delta = Time.deltaTime;

            delayDel.Clear();

            foreach (var kv in timers) {
                Info info = kv.Value;
                info.next -= (info.ignoreTimeScale ? unscaled : delta);

                if (info.next <= 0) {
                    info.proc.Invoke();

                    if (info.loop > 0) {
                        info.next = info.loop;
                    } else {
                        delayDel.Add(info.id);
                    }
                }                
            }

            foreach (var id in delayDel) timers.Remove(id);
        }

        class Info {
            public uint id = 0;
            public float loop = 0;
            public float next = 0;
            public bool ignoreTimeScale = true;
            public System.Action proc = null;
        }

        #region PRIVATES
        static Timer ins = null;
        static uint allocId = 0;
        static Dictionary<uint, Info> timers = new Dictionary<uint, Info>();
        static List<uint> delayDel = new List<uint>();
        #endregion
    }
}
